package de.dopichaj.labrador.search.merge;

import java.util.SortedSet;

import org.apache.log4j.Logger;

import de.dopichaj.labrador.search.hit.Hit;


/**
 * The implementation of Merge we used for our INEX 2005 submissions.
 */
public class FuzzyMerge extends Merge {
    
    private static final Logger log = Logger.getLogger(FuzzyMerge.class);

    /**
     * <pre>
     * 1        _____
     *         /
     * 0 _____/
     *       l  u
     * </pre> 
     */
    private double rampUp(int value, int lower, int upper) {
        return
            value <= lower
            ? 0
            : value >= upper
            ? 1
            : (double)(value - lower) / (upper - lower);
    }
    
    private double rampDown(int value, int lower, int upper) {
        return 1.0 - rampUp(value, lower, upper);
    }

    private double shortness(int length) {
        return rampDown(length, 10, 20);
    }
    
    private double veryshortness(int length) {
        return rampDown(length, 1, 8);
    }
    
    private double longness(int length) {
        return rampUp(length, 10, 50);
    }
    
    private double sigmoid(double x, double scale, double shift) {
        return 1.0 / (1.0 + Math.exp(scale * -(x - shift)));
    }
    
    private double greaterScore(double s1, double s2) {
        return sigmoid(s1/s2, 20, 1.25);
    }
    
    private double greaterOrEqualScore(double s1, double s2) {
        return sigmoid(s1/s2, 40, 0.8);
    }
    
    private double isZeroScore(double s) {
        return sigmoid(s, -100, 0.1);
    }
    
    public int decideMerge(Hit parent, SortedSet<Hit> children) {
        
        if (children.size() == 0) {
            return DO_NOT_MERGE;
        }
        
        int parentPos      = parent.getPosition();
        int parentLength   = parent.getContentLength();
        double parentScore = parent.getScore();
        Hit firstChild  = children.first();
        int firstLength = firstChild.getContentLength();
        double inlinenessSum = 0.0;
        int childrenLength = 0;
        double shortnessSum = 0.0;
        double peakArea = 0.0;
        double geqSum = 0.0;
        double gSum = 0.0;
        double childCount = 0.0;
        double highScore = 0.0;
        Hit highHit = null;
        for (Hit child : children) {
            int length = child.getContentLength();
            childrenLength += length;
            shortnessSum += veryshortness(length);
            int before = child.getPosition() - parentPos;
            int after = parentLength - before - length;
            double score = child.getScore();
            geqSum += greaterOrEqualScore(score, parentScore);
            gSum += greaterScore(score, parentScore);
            
            double inlineness = veryshortness(length) * (after > 0 ? 1.0 : 0.9) * (before > 0 ? 1.0 : 0.9);
            if (shortness(length) > 0.5) {
                tree.removeFromResults(child);
            } else {
                child.setScore((float)(score * Math.exp(1 * Math.log(1 - inlineness))));
            }
            
            inlinenessSum += inlineness;
            
            if (score > parentScore) {
                peakArea += (score - parentScore) * length;
            }
            
            // only count children with a score > 0
            childCount += 1 - isZeroScore(score);
            
            if (score > highScore) {
                highScore = score;
                highHit = child;
            }
        }
        log.debug("Avg inlineness for " + parent.getXPath() + ": " + inlinenessSum / children.size());
        
        // titles boost the score of their parents, but reduce their own score
        final double titleness =
            ((firstChild.getPosition() - parentPos) == 0 ? 1 : 0) *
            shortness(firstLength) *
            longness(parentLength - firstLength);
        if (firstChild.getScore() > parentScore) {
            parent.setScore((float)(parent.getScore() + titleness * Math.sqrt(firstChild.getScore() - parent.getScore())));
            parentScore = parent.getScore();
        }
        firstChild.setScore((float)(firstChild.getScore() * Math.pow(1 - titleness, 2.0)));
        
        double lenCoverage = Math.min((double)childrenLength / parentLength, 1.0);

        // single child
        if (children.size() == 1 && lenCoverage <= 0.8) {
            return MERGE_DOWN;
        }
        if (children.size() == 1 && lenCoverage > 0.8 &&
            greaterScore(firstChild.getScore(), parentScore) < 0.9) {
            
            return MERGE_UP;
        }
        
        // high coverage
        if (lenCoverage > 0.8) {
            if (highScore >= 0.8 * parentScore) {
                parent.setScore((float)(parentScore * 1.25));
                parentScore = parent.getScore();
            }
            if (parentScore < highScore) {
                double lenFrac = Math.min((double)highHit.getContentLength() / parentLength, 1.0);
                parent.setScore((float)(parentScore + (highScore - parentScore) * lenFrac));
                parentScore = parent.getScore();
            }
        }
        
        // if many children are very short, merge upwards
        if (shortnessSum / children.size() > 0.8) {
            parent.setScore((float)(parentScore * 1.25));
            return MERGE_UP;
        }
        
        return DO_NOT_MERGE;
    }

    @Override
    public String getDescription() {
        return "Fuzzy merge as in INEX 2005";
    }
}
/*
Copyright (c) 2005 Benedikt Eger
Copyright (c) 2005-2007 Philipp Dopichaj

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/